/*
 * Copyright (c) 2001, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing.text.html;

import javax.swing.text.*;
import java.io.Serializable;
import java.util.*;

/**
 * An implementation of <code>AttributeSet</code> that can multiplex
 * across a set of <code>AttributeSet</code>s.
 */
class MuxingAttributeSet implements AttributeSet, Serializable {

  /**
   * Creates a <code>MuxingAttributeSet</code> with the passed in
   * attributes.
   */
  public MuxingAttributeSet(AttributeSet[] attrs) {
    this.attrs = attrs;
  }

  /**
   * Creates an empty <code>MuxingAttributeSet</code>. This is intended for
   * use by subclasses only, and it is also intended that subclasses will
   * set the constituent <code>AttributeSet</code>s before invoking any
   * of the <code>AttributeSet</code> methods.
   */
  protected MuxingAttributeSet() {
  }

  /**
   * Directly sets the <code>AttributeSet</code>s that comprise this
   * <code>MuxingAttributeSet</code>.
   */
  protected synchronized void setAttributes(AttributeSet[] attrs) {
    this.attrs = attrs;
  }

  /**
   * Returns the <code>AttributeSet</code>s multiplexing too. When the
   * <code>AttributeSet</code>s need to be referenced, this should be called.
   */
  protected synchronized AttributeSet[] getAttributes() {
    return attrs;
  }

  /**
   * Inserts <code>as</code> at <code>index</code>. This assumes
   * the value of <code>index</code> is between 0 and attrs.length,
   * inclusive.
   */
  protected synchronized void insertAttributeSetAt(AttributeSet as,
      int index) {
    int numAttrs = attrs.length;
    AttributeSet newAttrs[] = new AttributeSet[numAttrs + 1];
    if (index < numAttrs) {
      if (index > 0) {
        System.arraycopy(attrs, 0, newAttrs, 0, index);
        System.arraycopy(attrs, index, newAttrs, index + 1,
            numAttrs - index);
      } else {
        System.arraycopy(attrs, 0, newAttrs, 1, numAttrs);
      }
    } else {
      System.arraycopy(attrs, 0, newAttrs, 0, numAttrs);
    }
    newAttrs[index] = as;
    attrs = newAttrs;
  }

  /**
   * Removes the AttributeSet at <code>index</code>. This assumes
   * the value of <code>index</code> is greater than or equal to 0,
   * and less than attrs.length.
   */
  protected synchronized void removeAttributeSetAt(int index) {
    int numAttrs = attrs.length;
    AttributeSet[] newAttrs = new AttributeSet[numAttrs - 1];
    if (numAttrs > 0) {
      if (index == 0) {
        // FIRST
        System.arraycopy(attrs, 1, newAttrs, 0, numAttrs - 1);
      } else if (index < (numAttrs - 1)) {
        // MIDDLE
        System.arraycopy(attrs, 0, newAttrs, 0, index);
        System.arraycopy(attrs, index + 1, newAttrs, index,
            numAttrs - index - 1);
      } else {
        // END
        System.arraycopy(attrs, 0, newAttrs, 0, numAttrs - 1);
      }
    }
    attrs = newAttrs;
  }

  //  --- AttributeSet methods ----------------------------

  /**
   * Gets the number of attributes that are defined.
   *
   * @return the number of attributes
   * @see AttributeSet#getAttributeCount
   */
  public int getAttributeCount() {
    AttributeSet[] as = getAttributes();
    int n = 0;
    for (int i = 0; i < as.length; i++) {
      n += as[i].getAttributeCount();
    }
    return n;
  }

  /**
   * Checks whether a given attribute is defined.
   * This will convert the key over to CSS if the
   * key is a StyleConstants key that has a CSS
   * mapping.
   *
   * @param key the attribute key
   * @return true if the attribute is defined
   * @see AttributeSet#isDefined
   */
  public boolean isDefined(Object key) {
    AttributeSet[] as = getAttributes();
    for (int i = 0; i < as.length; i++) {
      if (as[i].isDefined(key)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Checks whether two attribute sets are equal.
   *
   * @param attr the attribute set to check against
   * @return true if the same
   * @see AttributeSet#isEqual
   */
  public boolean isEqual(AttributeSet attr) {
    return ((getAttributeCount() == attr.getAttributeCount()) &&
        containsAttributes(attr));
  }

  /**
   * Copies a set of attributes.
   *
   * @return the copy
   * @see AttributeSet#copyAttributes
   */
  public AttributeSet copyAttributes() {
    AttributeSet[] as = getAttributes();
    MutableAttributeSet a = new SimpleAttributeSet();
    int n = 0;
    for (int i = as.length - 1; i >= 0; i--) {
      a.addAttributes(as[i]);
    }
    return a;
  }

  /**
   * Gets the value of an attribute.  If the requested
   * attribute is a StyleConstants attribute that has
   * a CSS mapping, the request will be converted.
   *
   * @param key the attribute name
   * @return the attribute value
   * @see AttributeSet#getAttribute
   */
  public Object getAttribute(Object key) {
    AttributeSet[] as = getAttributes();
    int n = as.length;
    for (int i = 0; i < n; i++) {
      Object o = as[i].getAttribute(key);
      if (o != null) {
        return o;
      }
    }
    return null;
  }

  /**
   * Gets the names of all attributes.
   *
   * @return the attribute names
   * @see AttributeSet#getAttributeNames
   */
  public Enumeration getAttributeNames() {
    return new MuxingAttributeNameEnumeration();
  }

  /**
   * Checks whether a given attribute name/value is defined.
   *
   * @param name the attribute name
   * @param value the attribute value
   * @return true if the name/value is defined
   * @see AttributeSet#containsAttribute
   */
  public boolean containsAttribute(Object name, Object value) {
    return value.equals(getAttribute(name));
  }

  /**
   * Checks whether the attribute set contains all of
   * the given attributes.
   *
   * @param attrs the attributes to check
   * @return true if the element contains all the attributes
   * @see AttributeSet#containsAttributes
   */
  public boolean containsAttributes(AttributeSet attrs) {
    boolean result = true;

    Enumeration names = attrs.getAttributeNames();
    while (result && names.hasMoreElements()) {
      Object name = names.nextElement();
      result = attrs.getAttribute(name).equals(getAttribute(name));
    }

    return result;
  }

  /**
   * Returns null, subclasses may wish to do something more
   * intelligent with this.
   */
  public AttributeSet getResolveParent() {
    return null;
  }

  /**
   * The <code>AttributeSet</code>s that make up the resulting
   * <code>AttributeSet</code>.
   */
  private AttributeSet[] attrs;


  /**
   * An Enumeration of the Attribute names in a MuxingAttributeSet.
   * This may return the same name more than once.
   */
  private class MuxingAttributeNameEnumeration implements Enumeration {

    MuxingAttributeNameEnumeration() {
      updateEnum();
    }

    public boolean hasMoreElements() {
      if (currentEnum == null) {
        return false;
      }
      return currentEnum.hasMoreElements();
    }

    public Object nextElement() {
      if (currentEnum == null) {
        throw new NoSuchElementException("No more names");
      }
      Object retObject = currentEnum.nextElement();
      if (!currentEnum.hasMoreElements()) {
        updateEnum();
      }
      return retObject;
    }

    void updateEnum() {
      AttributeSet[] as = getAttributes();
      currentEnum = null;
      while (currentEnum == null && attrIndex < as.length) {
        currentEnum = as[attrIndex++].getAttributeNames();
        if (!currentEnum.hasMoreElements()) {
          currentEnum = null;
        }
      }
    }


    /**
     * Index into attrs the current Enumeration came from.
     */
    private int attrIndex;
    /**
     * Enumeration from attrs.
     */
    private Enumeration currentEnum;
  }
}
